/****************************************************************************
 *
 *  LinuxChatty
 *  Copyright (C) 2010, David Hsu.
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 ***************************************************************************/
#include "HTTPClient.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>

using namespace std;

HTTPClient::HTTPClient():
	htmlError(NoError)
{
	// Default API server
	host = "";
	hostent = gethostbyname(host.c_str());
}

HTTPClient::~HTTPClient()
{
	// Nothing for now
}

void HTTPClient::SetHost(const std::string name)
{
	host = name;
	hostent = gethostbyname(host.c_str());
}

const string& HTTPClient::GetHost()
{
	return host;
}

const string& HTTPClient::GetBuffer()
{
	return buffer;
}

const string HTTPClient::GetResponse()
{
	char * match = strstr((char *)buffer.c_str(), "HTTP/1.1");
	if (match != NULL)
	{
		// The server response will be a 3 digit number after the 
		// HTTP/1.1 text
		char textResponse[4];
		memset(textResponse, 0, 4);
		strncpy(textResponse, match+9, 3);
		return textResponse;
	}
	
	// Shouldn't reach here, but if we do, return an invalid response
	return "";
}

const string HTTPClient::GetBody()
{
    // Looks for '\r\n\r\n' since the header ends with two newlines
    size_t index = buffer.find("\r\n\r\n");
    if (index != string::npos)
	{
		return buffer.substr(index+4);
	}
	return "";
}

const HTTPClient::Error HTTPClient::GetRecentError()
{
	return htmlError;
}

void HTTPClient::ClearError()
{
	htmlError = NoError;
}

int HTTPClient::CommDownload(const string path, const string& post_data,
    const string auth)
{
	ClearError();
	
	// If there's no host set yet, then we can't send or receive anything yet
	if (host.empty() || hostent == NULL)
	{
		htmlError = NoHost;
		return -2;
	}
	
	struct sockaddr_in addr_in;
	int sockfd;
	string request;
	//char*              request;
	char*              response      = NULL;
	unsigned int       response_size = 0;
	unsigned int       expected_size = 0;
	char               response_buffer[4096];
	char*              match;
	int                recv_bytes;
	buffer.clear();
	
	// Connect to the server
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	
	// If the socket couldn't be created, then the rest will fail
	if (sockfd < 0)
	{
		printf("Something wrong with the socket!\n");
		htmlError = ErrorCreatingSocket;
		return -4;
	}
	
	memset(&addr_in, 0, sizeof(addr_in));
	addr_in.sin_family      = AF_INET;
	addr_in.sin_addr.s_addr = ((struct in_addr *)(hostent->h_addr_list[0]))->s_addr;
	addr_in.sin_port        = htons(80);
	
	if (connect(sockfd, (struct sockaddr*)&addr_in, sizeof(addr_in)) < 0)
	{
		htmlError = UnableToConnectWithSocket;
		return -5;
	}
	
	/* Send the HTTP request. */
	if (post_data.empty())
	{
		request = "GET " + path + " HTTP/1.1\r\nHost: " + host + "\r\n\r\n";
	}
	else
	{
		// Arbitrary length for the post length digits.
		char postLength[16];
		sprintf(postLength, "%d", post_data.length());
		request = "POST " + path + " HTTP/1.1\r\nHost: " + host + "\r\n"
				"Authorization: Basic " + auth + "\r\n"
				"Content-Type: application/x-www-form-urlencoded\r\n"
				"Content-Length: " + postLength + "\r\n\r\n" + post_data;
	}
	
	if (send(sockfd, request.c_str(), request.length(), 0) < 0)
	{
		close(sockfd);
		htmlError = UnableToSendToSocket;
		return -3;
	}
    
    // Timeout information for the select below	
    struct timeval tv;
    fd_set readfds;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    
    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
	    
	// Get the response.
	do
	{
	    // Do a select in case the connection times out so it doesn't block
	    // on receiving data.
	    select(sockfd+1, &readfds, NULL, NULL, &tv);
	    
	    if (FD_ISSET(sockfd, &readfds))
	    {
		    recv_bytes = recv(sockfd, response_buffer, 4096, 0);
		}
		else
		{
		    // Connection timed-out
		    recv_bytes = 0;
		    htmlError = ConnectionTimedOut;
		}

		if (recv_bytes > 0)
		{
			// Resize the response buffer and copy the newly received bytes in.
			response_size += recv_bytes;
			response       = (char *)realloc(response, response_size + 1);
			memcpy(response + response_size - recv_bytes, 
				response_buffer, 
				recv_bytes);
			response[response_size] = 0; /* Null terminate the response. */
		}
		else if (recv_bytes < 0)
		{
			// Something has gone wrong.
			close(sockfd);
			htmlError = ReceivedInvalidData;
			return -3;
		}

		// See if we've read the headers yet.
		if (expected_size == 0 && response != NULL)
		{
			// If we've begun the content, then the headers have all been read.
			match = strstr(response, "<?xml");
			if (match != NULL)
			{
				// Get the length of the HTTP header.
				expected_size = (unsigned int)(match - response);

				// Add to that the length of the XML content.
				match = strstr(response, "Content-Length: ");
				match += strlen("Content-Length: ");
				expected_size += atoi(match);
			}
		}
	} while (recv_bytes > 0 && 
			(expected_size == 0 || response_size < expected_size));

	close(sockfd);

	// Check for a timeout
	if (response_size == 0)
	{
		htmlError = ConnectionTimedOut;
		return -3;
	}

	buffer = response;
	
	return 0;
}
